// Copyright (c) 2020 Presto Labs Pte. Ltd.
// Author: fengyang
#pragma once

#include <iostream>
#include <optional>
#include <set>
#include <sstream>
#include <string>
#include <vector>
#include <memory>

#include <boost/date_time/posix_time/posix_time.hpp>

#include "coin2/exchange/base/symbology/derivative_util.h"
#include "coin2/exchange/base/symbology/futures_product.h"
#include "coin2/exchange/bybit_futures/symbology/currency.h"
#include "coin2/exchange/bitmex/symbology/product.h"

namespace coin2::exchange::bybit_futures::symbology {
using ExchangeType = base::market::ExchangeType;
using coin2::exchange::bybit_futures::symbology::BybitFuturesCurrency;
using coin2::exchange::bitmex_futures::symbology::BitmexFuturesProduct;

class BybitFuturesProduct
    : public base::symbology::FuturesProduct<BybitFuturesCurrency, BybitFuturesProduct>,
      public base::symbology::ProductFromStrMixin<BybitFuturesProduct> {
 public:
  using FuturesProduct = base::symbology::FuturesProduct<BybitFuturesCurrency, BybitFuturesProduct>;
  using ContractType = base::symbology::ContractType;

 public:
  BybitFuturesProduct(
      const BybitFuturesCurrency& base,
      const BybitFuturesCurrency& quote,
      const std::string& native_product,
      const std::string& native_subscription_symbol,
      const ContractType contract_type,
      const int64_t expiry_timestamp,
      const int64_t resolve_timestamp)
      : FuturesProduct(
            base,
            quote,
            native_product,
            native_subscription_symbol,
            base::market::ExchangeType::Bybit,
            contract_type,
            expiry_timestamp,
            resolve_timestamp) {}

 public:
  static std::unique_ptr<BybitFuturesProduct> FromNativeStrImpl(
      const std::string& native_product,
      const int64_t resolve_timestamp) {
    std::vector<std::string> out;
    std::unique_ptr<BybitFuturesCurrency> base;
    std::unique_ptr<BybitFuturesCurrency> quote;

    std::string last2 = native_product.substr(native_product.size() - 2);
    std::string expiry_str;
    std::string base_quote_str;
    if (std::all_of(std::begin(last2), std::end(last2), ::isdigit)) {
      CalculateContractType(
          native_product.substr(native_product.size() - 3, 3),
          resolve_timestamp,
          &expiry_str);
      base_quote_str = native_product.substr(0, native_product.size() - 3);
    } else {
      expiry_str = "PERPETUAL";
      base_quote_str = native_product;
    }
    if (base_quote_str.substr(base_quote_str.size() - 3) == "USD") {
      base = BybitFuturesCurrency::FromNativeStrNoThrow(
          base_quote_str.substr(0, base_quote_str.length() - 3), resolve_timestamp);
      if (!base) {
        return {};
      }
      quote = BybitFuturesCurrency::FromNativeStrNoThrow("USD", resolve_timestamp);
    } else if (base_quote_str.substr(base_quote_str.length() - 4) == "USDT") {
      base = BybitFuturesCurrency::FromNativeStrNoThrow(
          base_quote_str.substr(0, base_quote_str.length() - 4), resolve_timestamp);
      if (!base) {
        return {};
      }
      quote = BybitFuturesCurrency::FromNativeStrNoThrow("USDT", resolve_timestamp);
    } else if (base_quote_str.substr(base_quote_str.length() - 4) == "PERP") {
      // BTCPERP
      base = BybitFuturesCurrency::FromNativeStrNoThrow(
          base_quote_str.substr(0, base_quote_str.length() - 4), resolve_timestamp);
      if (!base) {
        return {};
      }
      quote = BybitFuturesCurrency::FromNativeStrNoThrow("USDC", resolve_timestamp);
    } else {
      return {};
    }

    std::string native_product_tmp;
    std::string native_subscription_symbol;
    ContractType contract_type;
    int64_t expiry_timestamp;
    bool success = CalculateParams(
        *base, *quote, expiry_str, resolve_timestamp,
        &native_product_tmp, &native_subscription_symbol,
        &contract_type, &expiry_timestamp);
    if (success) {
      DCHECK_EQ(native_product_tmp, native_product);
      return std::make_unique<BybitFuturesProduct>(
          *base, *quote, native_product, native_subscription_symbol,
          contract_type, expiry_timestamp, resolve_timestamp);
    } else {
      return {};
    }
  }

  static bool CalculateParams(
      const BybitFuturesCurrency& base,
      const BybitFuturesCurrency& quote,
      const std::string& expiry_str,
      const int64_t resolve_timestamp,
      std::string* native_product,
      std::string* native_subscription_symbol,
      ContractType* contract_type,
      int64_t* expiry_timestamp) {
    using base::symbology::ContractType;
    using base::symbology::StringToContractType;
    using base::symbology::IsDateStr;
    using base::symbology::FindExpiryFriday;
    using base::symbology::ToDateStr;

    if (IsDateStr(expiry_str)) {
      bool found = false;
      for (auto type : {ContractType::QUARTER, ContractType::NEXT_QUARTER}) {
        auto timestamp = FindExpiryFriday(
            ExchangeType::Bybit, type, resolve_timestamp, expiry_time_of_day_nanosec);
        if (expiry_str == ToDateStr(timestamp)) {
          *contract_type = type;
          *expiry_timestamp = timestamp;
          found = true;
          break;
        }
      }
      if (!found) {
        return false;
      }
    } else {
      auto type = StringToContractType(expiry_str);
      if (type) {
        *contract_type = type.value();
        if (*contract_type == ContractType::PERPETUAL) {
          *expiry_timestamp = FuturesProduct::PERPETUAL_TIMESTAMP;
        } else {
          *expiry_timestamp = FindExpiryFriday(
              ExchangeType::Bybit, *contract_type, resolve_timestamp, expiry_time_of_day_nanosec);
        }
      } else {
        return false;
      }
    }

    std::stringstream ss;
    if (*contract_type == ContractType::PERPETUAL &&
        quote.native_currency() == "USDC") {
        ss << base.native_currency() << "PERP";
    } else if (*contract_type == ContractType::PERPETUAL) {
      ss << base.native_currency() << quote.native_currency();
    } else {
      using namespace boost::posix_time;
      auto expiry_date = from_time_t(::ceil(*expiry_timestamp / 1e9)).date();
      if (quote.native_currency() == "USDC") {
        std::string month_short_str(expiry_date.month().as_short_string());
        ss << base.native_currency()
          << "-"
          << expiry_date.day()
          << *StringToUpperInPlace(&month_short_str)
          << std::to_string(expiry_date.year() % 100);
      } else {
        ss << base.native_currency()
           << quote.native_currency()
           << BitmexFuturesProduct::MonthToCode(expiry_date.month())
           << std::to_string(expiry_date.year() % 100);    
      }
    }
    *native_product = ss.str();
    *native_subscription_symbol = *native_product;
    return true;
  }

  static int64_t FindExpiryQuarter(const int64_t resolve_timestamp) {
    int current_year;
    int current_month;
    BitmexFuturesProduct::GetMonthYearFromTimestamp(
        resolve_timestamp, &current_year, &current_month);
    auto expiry_month = current_month + (12 - current_month) % 3;

    using namespace boost::posix_time;
    using namespace boost::gregorian;
    using namespace coin2::exchange::base::symbology;

    auto possible_expiry_date =
        last_day_of_the_week_in_month(Friday, expiry_month).get_date(current_year);
    const auto td = microseconds(expiry_time_of_day_nanosec / 1000);
    int64_t possible_expiry_timestamp = DatetimeToTimestamp(ptime(possible_expiry_date, td));
    if (resolve_timestamp < possible_expiry_timestamp) {
      return possible_expiry_timestamp;
    } else {
      return FindExpiryQuarter(possible_expiry_timestamp + 75 * 24 * 3600 * 1e9);
    }
  }

  static int64_t FindExpiryNextQuarter(const int64_t resolve_timestamp) {
    int64_t quarter_expiry_timestamp = FindExpiryQuarter(resolve_timestamp);
    return FindExpiryQuarter(quarter_expiry_timestamp + 75 * 24 * 3600 * 1e9);
  }

  static bool CalculateContractType(const std::string& short_expiry_code,
                                    const int64_t resolve_timestamp, std::string* expiry_str) {
    using base::symbology::ContractType;

    int64_t quarter_expiry_timestamp = FindExpiryQuarter(resolve_timestamp);
    int quarter_month;
    int quarter_year;
    BitmexFuturesProduct::GetMonthYearFromTimestamp(
        quarter_expiry_timestamp, &quarter_year, &quarter_month);
    std::string calc_code = BitmexFuturesProduct::MonthToCode(quarter_month) +
        std::to_string(quarter_year % 100);
    if (calc_code == short_expiry_code) {
      *expiry_str = "QUARTER";
      return true;
    } else {
      int64_t next_quarter_expiry_timestamp =
          FindExpiryQuarter(quarter_expiry_timestamp + 75 * 24 * 3600 * 1e9);
      BitmexFuturesProduct::GetMonthYearFromTimestamp(
          next_quarter_expiry_timestamp, &quarter_year, &quarter_month);
      calc_code = BitmexFuturesProduct::MonthToCode(quarter_month) +
          std::to_string(quarter_year % 100);
      if (calc_code == short_expiry_code) {
        *expiry_str = "NEXT_QUARTER";
        return true;
      }
    }
    return false;
  }

  static const int64_t expiry_time_of_day_nanosec = 8 * 3600 * 1e9;
};

}  // namespace coin2::exchange::bybit_futures::symbology
